package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.product.model.Test;
import com.atguigu.gmall.product.mapper.TestMapper;
import com.atguigu.gmall.product.service.TestService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.security.spec.MGF1ParameterSpec;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 业务实现类
 *
 * @author atguigu
 * @since 2022-12-23
 */
@Service
public class TestServiceImpl extends ServiceImpl<TestMapper, Test> implements TestService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试本地锁 synchronized
     * 1.从redis中获取num数据（数字）
     * 2.对num值进行自增加一操作
     */
    /*@Override
    public synchronized void testLock() {
        //1.先从redis中通过key num获取值  key提前手动设置 num 初始值：0
        String value = stringRedisTemplate.opsForValue().get("num");
        //2.如果值为空则非法直接返回即可
        if (StringUtils.isBlank(value)) {
            return;
        }
        //3.对num值进行自增加一
        int num = Integer.parseInt(value);
        stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
    }*/


    /**
     * 采用SpringDataRedis实现分布式锁
     * 原理：执行业务方法前先尝试获取锁（setnx存入key val），如果获取锁成功再执行业务代码，业务执行完毕后将锁释放(del key)
     */
    //@Override
    //public void testLock() {
    //
    //    //0.先尝试获取锁 setnx key val
    //    //问题：锁可能存在线程间相互释放
    //    //Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock", 10, TimeUnit.SECONDS);
    //    //解决：锁值设置为uuid
    //    String uuid = UUID.randomUUID().toString();
    //    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
    //
    //    if(flag){
    //        //获取锁成功，执行业务代码
    //        //1.先从redis中通过key num获取值  key提前手动设置 num 初始值：0
    //        String value = stringRedisTemplate.opsForValue().get("num");
    //        //2.如果值为空则非法直接返回即可
    //        if (StringUtils.isBlank(value)) {
    //            return;
    //        }
    //        //3.对num值进行自增加一
    //        int num = Integer.parseInt(value);
    //        stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
    //
    //        //4.将锁释放 判断uuid
    //        //问题：删除操作缺乏原子性。
    //        //if(uuid.equals(stringRedisTemplate.opsForValue().get("lock"))){ //线程一：判断是满足是当前线程锁的值
    //        //    //条件满足，此时锁正好到期，redis锁自动释放了线程2索取锁成功
    //        //    stringRedisTemplate.delete("lock");
    //        //}
    //        //解决：redis执行lua脚本保证原子，lua脚本执行会作为一个整体执行
    //
    //        //执行脚本参数 参数1：脚本对象封装lua脚本，参数二：lua脚本中需要key参数（KEYS[i]）  参数三：lua脚本中需要参数值 ARGV[i]
    //        //4.1 先创建脚本对象 DefaultRedisScript泛型脚本语言返回值类型 Long 0：失败 1：成功
    //        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    //        //4.2设置脚本文本
    //        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
    //                "then\n" +
    //                "    return redis.call(\"del\",KEYS[1])\n" +
    //                "else\n" +
    //                "    return 0\n" +
    //                "end";
    //        redisScript.setScriptText(script);
    //        //4.3 设置响应类型
    //        redisScript.setResultType(Long.class);
    //        stringRedisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);
    //    }else{
    //        try {
    //            Thread.sleep(100);
    //            this.testLock();
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }
    //    }
    //}


    @Autowired
    private RedissonClient redissonClient;

    /**
     * 使用Redison实现分布式锁
     * 开发步骤：
     * 1.使用RedissonClient客户端对象 创建锁对象
     * 2.调用获取锁方法
     * 3.执行业务逻辑
     * 4.将锁释放
     */
    @GmallCache
    public void testLock() {

        //0.创建锁对象
        RLock lock = redissonClient.getLock("lock1");

        try {
            //0.1 尝试加锁
            //0.1.1 lock() 阻塞等待一直到获取锁,默认锁有效期30s
            //lock.lock();
            //0.1.2 lock(数字，时间单位) 指定获取锁成功有效期，直到获取锁成功。到期自动释放
            //lock.lock(10, TimeUnit.SECONDS);
            //0.1.3 tryLock(等待获取锁时间，锁的有效期，时间单位) 指定时间内如果获取锁成功，返回true 执行后续业务，如果超过等待时间，返回false
            boolean flag = lock.tryLock(2, 10, TimeUnit.SECONDS);
            if (flag) {
                System.out.println(Thread.currentThread().getName() + "线程获取锁成功");
                //获取成功 执行业务
                //1.先从redis中通过key num获取值  key提前手动设置 num 初始值：0
                String value = stringRedisTemplate.opsForValue().get("num");
                //2.如果值为空则非法直接返回即可
                if (StringUtils.isBlank(value)) {
                    return;
                }
                //3.对num值进行自增加一
                int num = Integer.parseInt(value);
                stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));

                //测试可重入锁
                this.check();

                //4.将锁释放
                lock.unlock();
            } else {
                //本地获取锁失败
                Thread.sleep(100);
                this.testLock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            //如果执行异常，释放锁
            lock.unlock();
        }
    }


    /**
     * hash  redisKey(锁名称)  hashKey（线程信息）  hashval(可重入次数)
     * 测试可重入
     */
    private void check() {
        //尝试获取锁
        RLock lock = redissonClient.getLock("lock1");
        lock.lock();

        //chekck1();

        //执行业务
        System.out.println(Thread.currentThread().getName() + "可重入获取成功");

        //释放锁
        lock.unlock();
    }


    /**
     * 从Redis中读数据
     *
     * @return
     */
    @Override
    public String read() {
        //1.创建读写锁对象
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWriteLock");
        //2.获取读锁
        RLock lock = readWriteLock.readLock();
        //加锁 给锁的有效期设置为10s
        lock.lock(10, TimeUnit.SECONDS);

        //3.执行从redis获取数据业务
        String msg = stringRedisTemplate.opsForValue().get("msg");

        //4.释放读锁
        //lock.unlock();
        return msg;
    }

    /**
     * 将数据写入redis
     *
     * @return
     */
    @Override
    public void write() {
        //1.创建读写锁对象
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWriteLock");
        //2.获取写锁
        RLock lock = readWriteLock.writeLock();
        //加锁 给锁的有效期设置为10s
        lock.lock(10, TimeUnit.SECONDS);

        //3.业务将数据写入redis
        stringRedisTemplate.opsForValue().set("msg", "msgData");

        //4.释放写锁
        //lock.unlock();

    }

    public static void main(String[] args) {
        Object[] ags = new Object[]{1};
        String collect = Arrays.asList(ags).stream().map(ag -> {
            return ag.toString();
        }).collect(Collectors.joining("|"));
        System.out.println(collect);
    }
}
